package com.example.util;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁工具类
 * 
 * @author HP
 * @description 基于Redis实现的分布式锁，用于防止并发预约冲突
 * @createDate 2025-08-26 20:30:00
 */
@Component
public class DistributedLock {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String SEAT_LOCK_PREFIX = "SEAT_RESERVATION_LOCK:";
    private static final String LECTURE_LOCK_PREFIX = "LECTURE_REGISTRATION_LOCK:";
    private static final String UNLOCK_SCRIPT = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) " +
            "else return 0 end";

    /**
     * 尝试获取座位锁
     * 
     * @param seatId 座位ID
     * @param timeout 超时时间（秒）
     * @return 锁标识，获取失败返回null
     */
    public String tryLock(Long seatId, int timeout) {
        String lockKey = SEAT_LOCK_PREFIX + seatId;
        String lockValue = UUID.randomUUID().toString();
        
        Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, timeout, TimeUnit.SECONDS);
        
        return Boolean.TRUE.equals(success) ? lockValue : null;
    }

    /**
     * 尝试获取讲座报名锁
     * 
     * @param lectureId 讲座ID
     * @param timeout 超时时间（秒）
     * @return 锁标识，获取失败返回null
     */
    public String tryLectureLock(Long lectureId, int timeout) {
        String lockKey = LECTURE_LOCK_PREFIX + lectureId;
        String lockValue = UUID.randomUUID().toString();
        
        Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, timeout, TimeUnit.SECONDS);
        
        return Boolean.TRUE.equals(success) ? lockValue : null;
    }

    /**
     * 释放座位锁
     * 
     * @param seatId 座位ID
     * @param lockValue 锁标识
     * @return 是否释放成功
     */
    public boolean releaseLock(Long seatId, String lockValue) {
        String lockKey = SEAT_LOCK_PREFIX + seatId;
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
        
        return Long.valueOf(1L).equals(result);
    }

    /**
     * 释放讲座锁
     * 
     * @param lectureId 讲座ID
     * @param lockValue 锁标识
     * @return 是否释放成功
     */
    public boolean releaseLectureLock(Long lectureId, String lockValue) {
        String lockKey = LECTURE_LOCK_PREFIX + lectureId;
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), lockValue);
        
        return Long.valueOf(1L).equals(result);
    }

    /**
     * 执行带座位锁的操作
     * 
     * @param seatId 座位ID
     * @param timeout 锁超时时间（秒）
     * @param action 要执行的操作
     * @param <T> 返回值类型
     * @return 操作结果
     * @throws Exception 操作异常或获取锁失败
     */
    public <T> T executeWithLock(Long seatId, int timeout, LockAction<T> action) throws Exception {
        String lockValue = tryLock(seatId, timeout);
        if (lockValue == null) {
            throw new RuntimeException("获取座位锁失败，请稍后重试");
        }

        try {
            return action.execute();
        } finally {
            releaseLock(seatId, lockValue);
        }
    }

    /**
     * 执行带讲座锁的操作
     * 
     * @param lectureId 讲座ID
     * @param timeout 锁超时时间（秒）
     * @param action 要执行的操作
     * @param <T> 返回值类型
     * @return 操作结果
     * @throws Exception 操作异常或获取锁失败
     */
    public <T> T executeWithLectureLock(Long lectureId, int timeout, LockAction<T> action) throws Exception {
        String lockValue = tryLectureLock(lectureId, timeout);
        if (lockValue == null) {
            throw new RuntimeException("讲座报名人数较多，请稍后重试");
        }

        try {
            return action.execute();
        } finally {
            releaseLectureLock(lectureId, lockValue);
        }
    }

    /**
     * 锁操作接口
     */
    @FunctionalInterface
    public interface LockAction<T> {
        T execute() throws Exception;
    }
}